﻿using System;
using System.Threading;
using NUnit.Framework;
using PoolKit.Commands;

namespace PoolKit.Tests
{
    [TestFixture]
    internal class AsyncCommandProcessorTests
    {
        [Test]
        public void MakeSureYouCannotDoJackWithDisposedCommandProcessor()
        {
            ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased();

            commandProcessor.Dispose();

            DisposablePatternTester.MakeSureDisposedObjectIsUnusable(commandProcessor);
        }

        [Test]
        [Timeout(5000)]
        public void MakeSureProcessorRunsAllCommandsThatRegisterToWaitForSomething()
        {
            ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased();

            var semaphore = new Semaphore(1, 1);
            const int commandCount = 200;

            var allCommandsExecuted = new CountdownEvent(commandCount);

            for (int i = 0; i < commandCount; i++)
            {
                commandProcessor.RegisterCommandWaitingForHandleSignal(
                    semaphore,
                    new ActionCommand(delegate()
                                          {
                                              Thread.Sleep(5);

                                              semaphore.Release();
                                              allCommandsExecuted.Signal();
                                          }));
            }

            allCommandsExecuted.Wait();
        }

        [Test]
        [Timeout(10000)]
        public void MakeSureCommandProcessorInitiatesRegisteredCommandOnlyWhenWaitHandleIsSet()
        {
            using (ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased())
            {
                var commandTriggeringEvent = new ManualResetEvent(false);
                var commandCompletionEvent = new ManualResetEvent(false);

                var command = new ActionCommand(
                    delegate
                    ()
                    {
                        Thread.Sleep(15);
                        commandCompletionEvent.Set();
                    });

                commandProcessor.RegisterCommandWaitingForHandleSignal(commandTriggeringEvent, command);

                // we haven't signaled the handle yet
                Assert.IsFalse(commandCompletionEvent.WaitOne(0));

                // here we go
                commandTriggeringEvent.Set();

                // eventually the command must run to completion (it's given 10 seconds to do so)
                commandCompletionEvent.WaitOne();
            }
        }

        [Test]
        public void MakeSureRegisteredCommandsDoGetCancelledWhenCommandProcessorIsDisposed()
        {
            using (ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased())
            {
                var commandTriggeringEvent = new ManualResetEvent(false);
                var commandCompletionEvent = new ManualResetEvent(false);

                var command = new ActionCommand(
                    delegate
                    ()
                    {
                        Thread.Sleep(15);
                        commandCompletionEvent.Set();
                    });

                commandProcessor.RegisterCommandWaitingForHandleSignal(commandTriggeringEvent, command);

                // we haven't signaled the handle yet
                Assert.IsFalse(commandCompletionEvent.WaitOne(0));

                commandProcessor.Dispose();

                // here we go, but this won't make any effect
                commandTriggeringEvent.Set();

                // the command will never run
                Assert.IsFalse(commandCompletionEvent.WaitOne(TimeSpan.FromMilliseconds(500)));
            }
        }

        [Test]
        [Timeout(5000)]
        public void MakeSureDelayedCommandsDoRun()
        {
            ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased();

            const int numberOfCommandsInTest = 100;

            int commandNumber = numberOfCommandsInTest;

            for (int i = 0; i < numberOfCommandsInTest; i++)
            {
                commandProcessor.RegisterDelayedCommand(
                    new ActionCommand(
                        delegate()
                        {
                            Interlocked.Decrement(ref commandNumber);
                        }),
                    TimeSpan.FromMilliseconds(150));
            }

            while (Interlocked.CompareExchange(ref commandNumber, -1, 0) != 0)
            {
                Thread.Sleep(200);
            }

            commandProcessor.Dispose();
        }

        [Test]
        public void MakeSureAllEnqueuedCommandsWithDelayRunToCompletionBeforeDisposeReturns()
        {
            ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased();

            const int numberOfCommandsInTest = 100;

            int commandNumber = numberOfCommandsInTest;

            for (int i = 0; i < numberOfCommandsInTest; i++)
            {
                commandProcessor.EnqueueCommand(new ActionCommand(
                    delegate()
                        {
                            Thread.Sleep(new Random().Next(15));
                            Interlocked.Decrement(ref commandNumber);
                        }));
            }

            commandProcessor.Dispose();

            int oldValue = Interlocked.CompareExchange(ref commandNumber, -1, 0);
            Assert.AreEqual(0, oldValue);
        }

        [Test]
        public void MakeSureAllCommandsEnqueuedWithSignificantDelayGetCancelledBeforeDisposeReturns()
        {
            ICommandProcessor commandProcessor = new CommandProcessorThreadPoolBased();

            const int numberOfCommandsInTest = 5;

            int commandNumber = numberOfCommandsInTest;

            for (int i = 0; i < numberOfCommandsInTest; i++)
            {
                commandProcessor.RegisterDelayedCommand(new ActionCommand(
                    delegate()
                    {
                        Interlocked.Decrement(ref commandNumber);
                    }), TimeSpan.FromMilliseconds(500));
            }

            // it should be able to cancel all five of them
            commandProcessor.Dispose();

            Thread.Sleep(1000);

            int oldValue = Interlocked.CompareExchange(ref commandNumber, -1, numberOfCommandsInTest);
            Assert.AreEqual(numberOfCommandsInTest, oldValue);
        }
    }
}